 Bonjour à tous, dans ce cours-là on va étudier l'ordonnancement de tâches.
 Donc là on fait l'hypothèse qu'on a un système en multiprogrammation.
 C'est-à-dire que la multiprogrammation, ça veut simplement dire que vous avez plusieurs tâches
 qui sont en concurrence pour l'accès au processeur.
 Donc l'ordonnancement, ce qui est du Lean en anglais, consiste à, parmi l'ensemble de ces tâches,
 choisir la tâche qu'on appelle la tâche élue pour lui donner l'accès au processeur.
 Donc il y a vraiment deux étapes dans un ordonnancement.
 Il faut choisir la tâche parmi l'ensemble des tâches prêtes.
 Il faut également assurer que toutes les tâches prêtes aient bien l'accès au processeur.
 Donc ce qu'on appelle, il faut éviter la famine.
 La famine, c'est le fait qu'une tâche soit prête et qu'elle ne soit jamais choisie.
 Il faut faire attention dans notre choix d'élection d'éviter la famine.
 Donc juste pour rappeler un petit peu ce qu'est une tâche, ce qu'on appelle le contexte d'une tâche.
 Donc une tâche, c'est un programme qui a été lancé,
 c'est un code auquel on associe un ensemble de variables.
 C'est ce qu'on appelle le contexte mémoire.
 Donc il y a trois zones dans une tâche, qui est illustrée dans ce schéma-là.
 Donc ça c'est le contexte en mémoire.
 Donc la première zone, c'est le code.
 Le code, ça contient la liste des instructions du programme.
 Ce code, il est alloué en mémoire et il est rempli à partir du code de l'exécutable qui se trouve sur le disque.
 Lorsque vous créez une tâche, le système va recopier une bonne partie de l'exécutable dans une zone mémoire qu'on appelle le code.
 Donc ce code, ce n'est pas suffisant, il ne suffit pas juste de recopier ce code.
 Ce code manipule un certain nombre de variables.
 Ces variables sont regroupées dans deux zones distinctes, la pile et les données.
 C'est dans la pile qu'on va stocker les variables locales, qui ont une durée de vie limitée.
 Donc elles vont être empilées et dépilées.
 Donc une pile, elle grossit en général dans le système, elle grossit dans ce sens-là.
 Donc au départ, elle est assez petite et elle va grossir au fur et à mesure qu'on définit des variables locales.
 Et dès qu'une fonction se termine, on va dépiler.
 Donc on empile, on dépile.
 Ici on va stocker toutes les variables locales.
 La deuxième zone, dans laquelle il n'y a pas cette notion d'empilement et dépilement,
 c'est une zone dans laquelle on va mettre les variables qui restent pendant toute la vie du programme.
 Ce sont des variables globales et ce sont toutes les variables qu'on alloue dans le tas.
 Le tas, c'est la zone où vous faites les mallocs.
 Lorsque vous allouez, vous créez une liste chêner ou un A, par exemple,
 vous faites des mallocs, et les mallocs vont se retrouver dans cette zone-là qu'on appelle les données.
 Tout ce qui est variable globale et données se retrouve dans cette zone-là.
 Tout ce qui est variable locale se retrouve dans cette zone-là.
 Donc on a ces trois zones mémoires pour chaque tâche qui a été créée.
 Le contexte mémoire, ce n'est pas suffisant pour caractériser une tâche,
 puisqu'il faut notamment connaître le contexte matériel, l'ensemble des registres.
 C'est bien beau d'avoir un code en mémoire, mais il faut savoir à quelle est la prochaine instruction exécutée dans ce code-là.
 C'est les registres qui permettent de donner cette information, ou le contexte matériel.
 Dans l'ensemble des registres, on a le programme compteur,
 qui indique la prochaine instruction exécutée parmi le code en mémoire,
 qu'on appelle PC ou RIP dans le monde Intel.
 C'est le registre de pointeur d'instruction, RIP.
 Il y a tout un ensemble de registres qui caractérise l'exécution courante de la tâche.
 Ces registres sont dépendants de l'architecture, mais il y a au moins le programme compteur,
 et puis un certain nombre de registres qui sont liés à l'architecture.
 Ça, c'est ce qu'on appelle le contexte matériel.
 Une tâche est également caractérisée par son état.
 C'est ce qu'on avait déjà un peu vu dans le premier cours.
 Une tâche peut être prête à s'exécuter, bloquée si jamais elle attend un événement,
 par exemple une entrée/sortie, une saisie sur le clavier, un paquet sur le réseau.
 Il y a toute une série d'événements qui peuvent faire en sorte que votre tâche n'est pas prête à s'exécuter, mais elle est bloquée.
 Et enfin, parmi les tâches qui sont prêtes à s'exécuter, il y en a une qui est élue.
 Je vous rappelle qu'on fait l'hypothèse qu'on est dans un système monocœur,
 c'est-à-dire qu'il n'y a qu'un seul cœur d'exécution, donc à un instant donné, je ne peux avoir qu'une seule tâche élue.
 Si on était en multicœur, par exemple si vous aviez 8 cœurs,
 la seule chose qui change c'est qu'à un instant donné, vous devriez avoir une seule tâche élue,
 vous en aurez 8 potentiellement.
 C'est ce qu'on appelle l'état d'une tâche.
 On a son contexte mémoire, l'ensemble de ses registres, son état,
 et il y a également pour chaque tâche un échéant unique,
 que vous pouvez visualiser lorsque vous faites la commande "ps" ou "top" dans Linux,
 ça vous affiche le PID, c'est l'identifiant unique de la tâche ou du processus.
 Pour nous, je vous rappelle aussi dans ce cours,
 les termes tâches ou processus peuvent être utilisés indifféremment.
 Donc vous avez tout ça, un ensemble de tâches, vous pouvez avoir des dizaines de tâches qui ont été créées,
 dans tous leurs contextes mémoire et leurs contextes matériels.
 L'ordre de nos heures consiste à pouvoir, parmi l'ensemble de ces tâches,
 passer d'une tâche à l'autre en fonction de leurs priorités.
 C'est ce qu'on appelle une commutation.
 La commutation, c'est le mécanisme qui fait passer d'une tâche T1 à une tâche T2.
 Une commutation, ça va consister à sauvegarder le contexte, l'ensemble des registres de l'ancienne tâche,
 et de restituer le contexte, l'ensemble des registres de la nouvelle tâche.
 Lorsqu'on commute, on sauvegarde le contexte matériel de T1 et on restaure le contexte matériel de T2.
 Donc le système stocke, en mémoire, l'ensemble des registres de toutes les tâches pour pouvoir commuter d'une tâche à l'autre.
 Maintenant, on va voir comment on choisit la tâche qu'on va élire.
 Il y a deux grands types d'ordonnancements qu'on a vu évoquer dans le premier cours.
 D'abord, les ordonnancements de type batch.
 Les types batch, c'est les ordonnancements dans lesquels on ne va pas partager le temps entre les tâches,
 et donc il n'y a pas cette notion de quantum qu'on avait déjà, dont on avait un petit peu parlé dans le premier cours.
 Dans un ordonnancement de type batch, une tâche élue va conserver le processeur jusqu'à ce qu'elle se détermine, bien sûr,
 qu'elle se bloque, ou bien, éventuellement, qu'elle soit réquisitionnée par une tâche plus prioritaire.
 Ce sont les trois seules conditions pour quitter le processeur.
 Lorsqu'une tâche, typiquement dans un ordonnancement de type batch,
 vous avez une file d'attente dans laquelle les tâches vont s'insérer à leur création.
 Ils s'insèrent dans la file d'attente.
 Parmi les tâches dans la file d'attente, une va être élue.
 On va voir selon quel critère d'élection.
 Par exemple, on va prendre la plus anciennement créée.
 C'est un des algorithmes possibles.
 Parmi l'ensemble des tâches qui ont été créées, on va en élire une.
 Cette tâche va rester sur le processeur.
 Si jamais elle fait une entrée-sortie, par exemple,
 elle va être mise hors-jeu temporairement.
 Elle va être injectée souvent dans une autre file, qui est la file des tâches bloquées.
 A ce moment-là, si T0 a été élue et fait une entrée-sortie,
 elle va être injectée dans la file des tâches bloquées.
 Et à ce moment-là, T1 va être élue, et ainsi de suite.
 Lorsque la tâche se termine, bien sûr, elle est hors-jeu.
 Si T1 se termine, elle est détruite.
 A ce moment-là, c'est T2 qui va prendre la main, et ainsi de suite.
 Lorsque l'entrée-sortie est terminée, donc la tâche qui était hors-jeu,
 lorsque T0 a terminé son entrée-sortie,
 elle va être réinjectée dans la file des tâches prêtes, et ainsi de suite.
 Donc ici, l'inconvénient des stratégies de type patch,
 tel qu'on l'avait vu déjà dans le premier cours,
 c'est que là, vous êtes très tributaires du comportement des tâches qui vous précèdent.
 Par exemple, si T1, imaginons que T1, ce soit une tâche qui fasse un gros calcul matricien,
 qui dure une heure.
 Imaginons que toutes les tâches ont la même priorité.
 Donc il n'y a aucune raison que T2 préempte requisition T1.
 Elles ont toutes la même priorité.
 Dans ce cas-là, qu'est-ce qui va se passer ?
 T1 va être élue.
 Comme elle ne fait pas d'entrée-sortie,
 elle va monopoliser le processeur pendant une heure.
 Et pendant ce temps-là, la tâche T2 va attendre une heure avant de pouvoir être élue.
 Donc si la tâche T2, c'est un simple LS qui dure quelques millisecondes,
 on va attendre plus d'une heure avant de s'exécuter,
 parce que devant nous, il y avait une tâche très longue.
 Donc rapidement, ce type d'organisme est très inconfortable pour l'utilisateur.
 Vous êtes complètement tributaires des tâches qui sont devant nous, dans la file d'attente.
 C'est pour ça que, si je résume un petit peu le graph d'état en mode batch,
 à la création, par défaut, il n'y a aucune raison lorsqu'on est créé qu'on ait directement accès au processeur.
 Par défaut, une fois que le contexte mémoire et le contexte matériel est créé,
 on passe à l'état "prêt".
 Au bout d'un moment, la tâche qui a été créée va être élue sur le processeur.
 Donc elle passe à l'état "élue".
 Là, on voit bien qu'elle peut quitter l'état "élue" soit lorsqu'elle se termine,
 ou lorsqu'elle se bloque, à ce moment-là, elle est à bloquer,
 ou bien, si jamais une tâche plus prioritaire est créée dans le système,
 à ce moment-là, la tâche éventuellement peut réquisitionner le processeur et le suivre à injecter à l'état "prêt".
 Une tâche, bien sûr, ne reste pas indéfiniment dans l'état "bloqué".
 Dès l'événement qu'elle attendait est arrivé, par exemple,
 si je demandais la fin de l'entrée-sortie est arrivé,
 je rebascule à l'état "prêt".
 Si je demandais une saisie au clavier,
 dès que la saisie de clavier est terminée, à ce moment-là, je rebascule à l'état "prêt".
 L'autre grand type d'ordonnancement, ça vise à combattre le phénomène que je vous ai indiqué tout à l'heure,
 le fait d'être bloqué complètement par une tâche très longue, par exemple.
 Donc ici, pour éviter ce genre de phénomène, on a introduit la notion de temps partagé.
 Ce sont les ordonnancements de type temps partagé.
 Donc là, on va définir une nouvelle notion, la notion de "quantum".
 Dans la notion de "quantum", on va définir un temps maximum d'exécution.
 Pour empêcher qu'une tâche monopolise le processeur,
 on va imposer qu'au bout d'un certain temps, la tâche courante soit réinjectée dans la liste des tâches prêtes,
 pour laisser la main à une autre tâche.
 C'est ce qu'on appelle un "quantum".
 Donc le "quantum", c'est un temps maximum d'exécution sur le processeur.
 Typiquement, ce temps est court, c'est une centaine, quelques centaines de millisecondes.
 Si je reprends l'exemple de tout à l'heure,
 imaginons que j'ai un calcul matriciel ici, et là j'ai la commande "ls".
 Mon calcul matriciel dure des heures de calcul, donc il va être élu,
 ce qui a été créé par exemple avant moi.
 Au bout de 100 millisecondes, ma tâche T1 va être réinjectée dans les tâches prêtes,
 et à ce moment-là, ma commande "ls" va pouvoir passer.
 Donc la commande "ls" qui avant attendait une heure avant de pouvoir s'exécuter,
 va juste attendre 100 millisecondes avant de pouvoir s'exécuter.
 C'est ce qui permet d'avoir un système beaucoup plus réactif, grâce à cette notion de "quantum".
 Après, les autres mécanismes restent les mêmes.
 Bien sûr, dès qu'une tâche, en temps partagé, se termine,
 elle est éjectée du processeur, on n'attend pas la fin de "quantum".
 Si la tâche T1 se termine, elle est directement éjectée du processeur,
 on donne directement la tâche T2.
 De même, si la tâche T1 fait une entrée-sortie,
 elle est mise hors-jeu temporairement, à ce moment-là, T2 peut passer.
 Donc ça, ça ne change pas par rapport aux stratégies batch.
 La seule chose qu'on rajoute, c'est une condition supplémentaire
 pour quitter l'état élu, ce qui est la fin de "quantum".
 Donc si, du coup, on fait le graphe des tâches qu'on avait déjà vu dans le premier cours,
 que j'ai remis ici,
 le graphe des tâches change peu, en fait.
 A la création, bien sûr, je suis créé à l'état prêt.
 Une fois que je suis prêt à m'exécuter, au bout d'un certain temps, je vais être élu.
 Donc là, on va zoomer sur les algorithmes d'élection juste après.
 Une fois que je suis élu, je peux quitter l'état élu lorsque je me termine,
 lorsque je me bloque, ou dès maintenant.
 Et on rajoute une troisième condition pour quitter l'état élu,
 c'est en cas de fin de "quantum".
 Donc en cas de fin de "quantum", la tâche ne s'est pas bloquée,
 mais on la réinjecte à l'état prêt pour laisser une chance à une autre tâche d'être élu.
 Je vais revenir sur cette petite notion de réquisition
 que j'ai un petit peu évoquée en mode batch, mais qui existe dans les deux stratégies.
 La notion de réquisition, ou préemption,
 on emploiera notamment en TD les deux termes,
 soit réquisition, soit préemption, ça veut dire la même chose.
 C'est la possibilité, pour une tâche prête,
 plus prioritaire d'interrompre la tâche élue pour réquisitionner le processeur,
 ou préempter le processeur.
 Le plus simple, c'est de voir ça sur un petit exemple.
 Imaginons que j'ai deux tâches, T1 et T2.
 On fait une stratégie toute simple en mode batch,
 sans temps partagé, donc sans "quantum".
 On suppose que la tâche T1 est plus prioritaire que la tâche T2.
 Donc un algorithme, qui va faire l'algorithme d'ordonnancement,
 c'est normal, il va choisir, les deux tâches sont prêtes,
 il choisit la tâche la plus prioritaire.
 Donc il choisit T1. T1 commence à s'exécuter sur le processeur.
 Donc là j'ai lu mon diagramme de bande.
 Donc T1 s'exécute, elle ne se bloque pas,
 pour l'instant elle s'exécute, il n'y a aucune raison qu'elle quitte le processeur.
 Donc elle reste élue, sauf qu'à un moment,
 elle demande une entrée/sortie.
 Donc là je vous ai dit, dès qu'elle demande une entrée/sortie,
 on va basculer à l'état bloqué.
 Et à ce moment là, le système va donner un ordre au contrôleur
 pour transférer les données en mémoire.
 Donc là on observe, le contrôleur est actif.
 À ce moment là, vu que T1 est basculé à l'état bloqué,
 le système va éliminer, il ne reste plus que T2 comme tâche prête.
 Donc c'est T2 qui va être élue.
 Même si T2 est moins prioritaire que T1,
 vu que T1 est à l'état bloqué,
 T1 ne peut pas être choisi comme tâche pour être élue.
 Donc T2 s'exécute.
 Vous remarquez bien que là il y a du vrai parallélisme
 entre le transfert et l'exécution de la tâche T2.
 Pourquoi ? Parce que ce sont deux processeurs différents qui travaillent.
 Ici c'est votre processeur central,
 votre Intel ou votre AMD.
 Ici c'est le contrôleur de votre disque,
 qui sont deux processeurs distincts.
 C'est pour ça que les deux sont vraiment en vrai parallélisme.
 Donc la tâche T2 est élue.
 Ensuite, qu'est-ce qui se passe lorsque l'entrée-sortie de T1 est terminée ?
 Donc ici, lorsqu'il y a la ferme entrée-sortie,
 ça lève une interruption,
 et dans le traitement de l'interruption,
 la tâche T2 va être rebasculée à l'état prêt.
 Donc là T2 et la tâche T1 sont de nouveau prêtes,
 à partir de ce moment là.
 Et on a vu que la priorité de T1 est supérieure à la priorité de T2.
 Mais comme c'est sans réquisition,
 on va laisser T2 s'exécuter, même si T1 est plus prioritaire.
 Il va falloir attendre, uniquement, comme il n'y a pas possibilité de réquisition,
 que T2 termine son exécution,
 pour enfin laisser la main à la tâche T1.
 Donc ça c'est dans une sortie sans réquisition.
 La tâche est élue, reste sur le processeur jusqu'à ce qu'elle se bloque ou elle se termine.
 Il n'y a pas d'autre condition.
 Lorsqu'on fait la même stratégie avec réquisition,
 donc le scénario change un peu ici,
 c'est-à-dire qu'on offre la possibilité à une tâche,
 dès qu'elle devient prête, de pouvoir prendre le processeur.
 On rajoute une troisième condition pour quitter le processeur,
 c'est l'existence d'une tâche prête plus prioritaire.
 Donc si je reprends le même scénario,
 directement le même scénario,
 mais en ayant une stratégie batch avec réquisition,
 qu'est-ce qui va se passer ?
 Au début ça ne change pas.
 T1 est plus prioritaire, donc elle est élue.
 Elle demande une entrée/sortie,
 donc elle passe à l'état bloqué pendant tout ce temps-là.
 T2 est bien sûr élue, il n'y a aucune raison que le processeur ne fasse rien.
 Donc T2 est élue.
 Mais lorsque la fin d'entrée/sortie arrive,
 T1 est rebasculé à l'état prêt,
 à ce moment-là T1 réquisitionne le processeur et va pouvoir s'exécuter.
 Donc là on voit que la tâche T1 va prendre la main,
 va directement être élue.
 Et la tâche T2 va reprendre sa réquisition que lorsque T1 sera terminée.
 Parce que T2 est moins prioritaire que T1.
 Donc ça c'est la notion de réquisition.
 Dans les stratégies qu'on verra en TD,
 on vous dira bien, par exemple j'ai une stratégie en mode batch,
 avec ou sans réquisition de priorité.
 Et on voit bien que lorsqu'on active la réquisition,
 les scénarios changent.
 Maintenant on va zoomer sur l'algorithme de choix.
 Comment l'algorithme, parmi les lanceurs de tâches prêtes,
 choisit la tâche la plus prioritaire.
 Pour pouvoir évaluer la qualité d'un gré de lancement,
 il y a un certain nombre de métriques qu'il va falloir évaluer.
 Donc les métriques, qu'est-ce qu'on mesure dans un gré de lancement ?
 On va mesurer le temps de réponse d'une tâche.
 Le temps de réponse d'une tâche, c'est le temps vraiment perçu par l'utilisateur.
 Lorsque vous tapez la commande ls, c'est entre le moment où vous faites le return
 et le moment où le ls s'affiche à l'écran.
 C'est ça qu'on appelle le temps de réponse.
 De manière plus formelle, le temps de réponse d'une tâche TI,
 c'est la date de fin de TI, moins la date de création de la tâche.
 On peut également illustrer le temps d'attente d'une tâche.
 C'est le temps où une tâche n'a pas été eue.
 Comment on calcule le temps d'attente d'une tâche ?
 Vous avez notre TTA ici.
 C'est son temps de réponse, moins la durée où la tâche s'exécute sur le CPU.
 Ça veut dire qu'en tout ce temps-là, elle ne s'est pas exécutée.
 Après, vous pouvez avoir des métriques qui indiquent si vos processeurs sont occupés ou pas.
 Vous pouvez avoir le taux d'occupation du processeur,
 ou le taux d'occupation de l'unité d'échange.
 J'appelle indifféremment l'unité d'échange ou le contrôleur.
 C'est la même chose pour nous.
 Le temps d'occupation du processeur, c'est le temps pendant lequel le processeur est occupé.
 C'est le temps total d'exécution de toutes les tâches.
 En dessous, vous faites la somme des durées de CPU des tâches.
 Le taux d'occupation de l'unité d'échange, c'est pareil.
 C'est la somme du temps que vous avez passé en entrée/sortie divisé par le temps total d'exécution.
 Maintenant, on va commencer à voir les algorithmes d'endorsement.
 D'abord, c'est en batch, puis c'est en temps partagé.
 Les algorithmes d'endorsement en batch, le plus répandu des algorithmes d'endorsement en batch,
 c'est l'endorsement qu'on appelle "premier arrivé, premier servi",
 qui a deux noms qu'on appelle très souvent "fifo".
 La première, "first in, first out", la première qui rentre dans le système, c'est la première qui va sortir du système.
 On appelle également aussi souvent "FCFS", "first come, first served".
 Tout ça, c'est le même algorithme.
 C'est l'algorithme le plus simple qu'on puisse imaginer.
 C'est que les tâches sont traitées dans leur ordre d'arrivée.
 C'est un peu l'exemple qu'on voyait, la file d'attente T1, T2, T3.
 Si T1 a été créée avant T2, puis T2 avant T3, ça va d'abord T1, puis T2, puis T3.
 Par exemple, ici, j'ai mis justement mes trois tâches T1, T2, T3, qui ont été créées dans cet ordre-là,
 à T0.
 T1 dure 24 secondes, T2 3 secondes, et T3 3 secondes.
 Donc j'ai une tâche longue et deux tâches courtes.
 Je suppose, pour simplifier, que je n'ai pas d'entrée-sortie ici.
 Tu vas me faire le renforcement.
 Vu que T1 a d'abord été créé, donc il est en tête dans la file d'attente, c'est T1 qui a la main.
 Dès que T1 s'est terminé, ça va être autour de T2.
 Dès que T2 s'est terminé, ça va être autour de T3.
 Vous voyez, c'est très simple comme stratégie.
 Alors là, ici, ensuite, si j'applique mes métriques,
 comment je calcule le temps de réponse et le temps d'attente,
 le temps de réponse de la tâche T1, c'est le temps,
 elle s'est terminée à l'instant 24, moins la date de création.
 Donc ça me fait 24 secondes.
 Le temps de réponse de T2, pareil, s'est terminé à l'instant 27, elle a été créée à l'instant 0.
 Donc c'est 27 secondes.
 Et le temps de réponse de la tâche T3, c'est,
 elle s'est terminée à 30 secondes, et elle a été créée à l'instant 0.
 Donc c'est 30 secondes.
 Pareil, là ici, on peut mesurer le temps d'attente.
 Donc vous prenez le temps de réponse et vous soustrayez le temps d'attente.
 Le temps d'attente, c'est plus parlant comme métrique,
 parce que ça ne veut pas dire que vous avez un temps de réponse élevé,
 que c'est mauvais.
 Par exemple, si vous avez un calcul matrifié qui dure une heure,
 c'est normal que votre temps de réponse soit d'une heure.
 Donc le temps d'attente, ça permet de mesurer comment,
 est-ce que ça a été efficace ou pas pour vous.
 Donc là on voit bien que la tâche T1 a été élue tout de suite,
 donc elle n'a pas du tout attendu.
 Donc ici je néglige les temps pour restaurer les rejets, sauvegarder, etc.
 Donc ici, le temps d'attente de la tâche T1 est de 0 seconde.
 Par contre, la tâche longue T1 n'a pas du tout attendu ici.
 Par contre, on voit que les deux tâches courtes, T2 et T3,
 elles ont beaucoup attendu.
 La tâche T2 attendue 24 secondes et la tâche T3 attendue 27 secondes.
 Donc si je calcule le temps moyen d'attente,
 on voit qu'il est élevé, le temps moyen d'attente,
 je fais la moyenne des temps d'attente, simplement,
 il est de 17 secondes.
 Et on observe ici que dans les stratégies plus courtes jobs d'abord,
 excusez-moi, dans les stratégies FIFO,
 les tâches courtes sont très pénalisées.
 Les tâches T2 et T3 ont beaucoup attendu,
 la tâche longue n'a peu attendu.
 Donc l'idée, c'est comment améliorer ça,
 comment je pourrais essayer d'améliorer ces stratégies-là.
 Donc l'intuition, l'idée la plus naturelle, c'est de se dire,
 si je connaissais le temps d'exécution de T1,
 il suffisait de faire passer T2 et T3 avant T1
 pour réduire les temps d'attente de manière conséquente.
 Donc si vous faites passer T2 et T3 avant T1,
 vous allez bien sûr allonger un petit peu le temps de T1,
 mais pas beaucoup, parce que ce sont des tâches courtes,
 par contre vous allez énormément réduire les temps d'attente de T2 et T3.
 C'est l'objet de la stratégie suivante,
 qui est beaucoup plus efficace, qu'on appelle la stratégie SJF,
 pour Shortest Job First,
 dans laquelle on va élire non pas les tâches dans leur ordre d'arrivée,
 mais on va d'abord choisir les tâches les plus courtes.
 Donc c'est les plus courtes jobs d'abord.
 Il y a deux noms qui s'utilisent, shorteuse SJF ou SJN.
 C'est la même chose.
 L'idée, c'est vraiment pour réduire le temps d'attente moyen,
 on va choisir la tâche dont le temps d'exécution restant est le plus court.
 Je suppose que je connais à l'avance le temps d'exécution des tâches,
 et je sais que T1 dure 24 secondes, T2 3 secondes, T3 3 secondes,
 et donc parmi l'ensemble de mes tâches prêtes,
 je choisis celle qui a le temps d'exécution restant le plus court.
 Donc là ici, j'ai un cas particulier où j'ai T2, T3 qui ont le même temps d'exécution restant,
 là j'en choisis une des deux.
 Généralement la première des deux qui a été créée.
 Vu que T2 a été créée avant T3, je vais d'abord choisir T2.
 Donc là ici, on voit que par rapport au scénario d'avant,
 le temps total d'exécution va être de 30 secondes.
 Donc ça, ça ne change pas son temps total d'exécution.
 Par contre, le temps d'attente va chuter.
 Donc si je reprends mes métriques de tout à l'heure, je recalcule le temps de réponse.
 Donc on voit bien que le temps de réponse de la tâche longue a augmenté.
 Avant il était de 24, il est passé de 24 à 30.
 Mais c'est une augmentation qui reste raisonnable.
 Donc lui, il attendait 0 seconde, maintenant il attend 6 secondes.
 Par contre, on voit qu'on a fait chuter complètement les temps de réponse des deux autres tâches.
 Le temps de réponse de la tâche 2 a chuté à 3,
 parce qu'il a tout de suite été élu. Il s'est terminé à l'instant 3, il a été créé à l'instant 0.
 Le temps de réponse de la tâche T3, lui, a aussi chuté.
 Donc là, ce n'est pas 30, c'est 6.
 Le temps de réponse de la tâche T3 a complètement chuté.
 Et si on fait la moyenne des temps d'attente, avant on avait 17 secondes,
 et bien là on retombe à 3 secondes le temps d'attente moyen.
 Donc on voit bien que la stratégie, si on compare les 3 secondes aux 17 secondes,
 la stratégie SJF est bien meilleure que la stratégie FIFO, ou FIFO ou FCFS, c'est la même chose.
 Donc globalement, quel que soit le scénario, on trouvera toujours des meilleurs temps d'attente,
 des temps d'attente plus faibles avec la stratégie SJF.
 Par contre, cette stratégie-là, dans la pratique, on ne l'implémente jamais,
 on n'implémente que du FIFO pratiquement.
 Pourquoi ? Parce que ça suppose une connaissance très forte.
 Je ne connais pas à l'avance le temps d'exécution des tâches.
 Quand je dis rarement connu, on fait compte d'un point de vue pratique.
 Même si dans la SJF, on le trouve dans tous les cours de système,
 on ne sait pas combien de temps va s'exécuter un programme.
 C'est très difficile de prédire à l'avance combien de temps va s'exécuter un programme.
 Même si un programme a un code très petit, si ce programme se met à boucler,
 même avec un code très petit, son temps d'exécution peut être très long.
 Donc c'est très difficile pour un système d'exploitation de savoir à l'avance
 ce que va faire un programme et ce qu'il va être ou pas.
 Donc ça c'est vraiment un inconvénient majeur de la stratégie.
 On sait qu'elle est meilleure.
 En général, dans une file d'attente, quand vous attendez un guichet,
 il vaut mieux faire passer ceux qui ont des temps de traitement le plus court.
 Donc globalement, pour le temps d'attente, c'est meilleur.
 Par contre, dans un système, on ne sait pas à l'avance combien de temps vont durer les tâches.
 Donc ça, rien que ça, c'est rédhibitoire pour implémenter une stratégie SJF.
 Le second problème de cette stratégie, qui est peut-être un peu moins gênant que cet inconvénient,
 c'est qu'il y a un risque de famine.
 Si vous avez des tâches courtes, les tâches courtes vont toujours doubler.
 Donc si en permanence vous créez des tâches T4, T5, T6 qui sont courtes,
 elles vont passer leur temps à doubler la tâche T1.
 Et T1 risque de ne jamais être doublée en permanence par les tâches courtes.
 Mais l'inconvénient majeur, c'est que cette stratégie, d'un point de vue pratique,
 on n'arrive pas à l'implémenter parce qu'on ne connaît pas à l'avance le temps d'exécution des tâches.
 Donc maintenant...
 Merci.
 Merci d'avoir regardé cette vidéo !
 Merci.
